/*
 * Copyright 2020-2030 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wwl.linked.list.linkedList;

import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Stream;

/**
 * 自己实现的循环双向链表.
 *
 * @author Created by Neil Wang
 * @version 1.0.0
 * @date 2021/6/24 4:33 下午
 */
public class LoopTwoWayLinkedListR<E> {
    private final Node<E> dummyHead;
    private final Node<E> dummyEnd;
    private Integer size;

    public LoopTwoWayLinkedListR() {
        this.dummyHead = new Node<>(null);
        this.dummyEnd = new Node<>(null);
        dummyHead.next = dummyEnd;
        dummyHead.prev = dummyEnd;
        dummyEnd.next = dummyHead;
        dummyEnd.prev = dummyHead;
        this.size = 0;
    }

    public LoopTwoWayLinkedListR(E... columns) {
        this();
        Arrays.stream(columns).forEach(this::addFirst);
    }

    public LoopTwoWayLinkedListR(Stream<E> dataStream) {
        this();
        dataStream.forEach(this::addFirst);
    }

    public Integer size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public void add(int index, E e) {
        if (index < 0 || index > size) throw new IndexOutOfBoundsException("Index out of bound exception.");
        if (previous(index)) addPositive(dummyHead, index, e);
        else addNegative(dummyEnd, index, e);
        size++;
    }

    public void addFirst(E e) {
        add(0, e);
    }

    public void addLast(E e) {
        add(size, e);
    }

    private Node<E> addNegative(Node<E> node, int index, E e) {
        if (index == size + 1) {
            node.next = new Node<>(e, node.next);
            node.next.prev = node;
            return node;
        }
        node.next = addNegative(node.prev, ++index, e);
        return node;
    }

    private Node<E> addPositive(Node<E> node, int index, E e) {
        if (index == -1) {
            node.prev = new Node<>(node.prev, e);
            node.prev.next = node;
            return node;
        }
        node.prev = addPositive(node.next, --index, e);
        return node;
    }

    public E get(int index) {
        if (index < 0 || index >= size) throw new IndexOutOfBoundsException("Index out of bound exception.");
        if (previous(index)) return getPositive(dummyHead, index);
        return getNegative(dummyEnd, index);
    }

    private E getNegative(Node<E> node, int index) {
        if (index == size + 1) return node.data;
        return getNegative(node.prev, ++index);
    }

    private E getPositive(Node<E> node, int index) {
        if (index == -1) return node.data;
        return getPositive(node.next, --index);
    }

    private boolean previous(int index) {
        return index < size >> 1;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("LoopTwoWayLinkedListR : ").append(" size : ").append(size).append(", top -> ");
        return toString(sb, 0).toString();
    }

    private StringBuilder toString(StringBuilder sb, int index) {
        if (index == size) return sb.append("end");
        return toString(sb.append(get(index)).append(" -> "), ++index);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        if (!size.equals(((LoopTwoWayLinkedListR<?>) o).size)) return false;
        return equals(true, (LoopTwoWayLinkedListR<?>) o, 0);
    }

    private boolean equals(boolean result, LoopTwoWayLinkedListR<?> that, int index) {
        if (index == size) return result;
        return equals(result && (Objects.equals(that.get(index), (get(index)))), that, ++index);
    }

    @Override
    public int hashCode() {
        int result = dummyHead != null ? dummyHead.hashCode() : 0;
        result = 31 * result + (dummyEnd != null ? dummyEnd.hashCode() : 0);
        result = 31 * result + (size != null ? size.hashCode() : 0);
        return result;
    }

    static class Node<E> {
        E data;
        Node<E> prev, next;

        public Node(E data) {
            this.data = data;
            this.next = null;
            this.prev = null;
        }

        public Node() {
            this.data = null;
            this.next = null;
            this.prev = null;
        }

        public Node(E data, Node<E> prev, Node<E> next) {
            this.data = data;
            this.prev = prev;
            this.next = next;
        }

        public Node(E data, Node<E> next) {
            this.data = data;
            this.next = next;
            this.next.prev = this;
        }

        public Node(Node<E> prev, E data) {
            this.prev = prev;
            this.data = data;
            this.prev.next = this;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Node<?> node = (Node<?>) o;
            if (!Objects.equals(data, node.data)) return false;
            if (!Objects.equals(prev, node.prev)) return false;
            return Objects.equals(next, node.next);
        }

        @Override
        public int hashCode() {
            int result = data != null ? data.hashCode() : 0;
            result = 31 * result + (prev != null ? prev.hashCode() : 0);
            result = 31 * result + (next != null ? next.hashCode() : 0);
            return result;
        }
    }

}
